from .base import BaseAction
import time
import math
from collections import deque

POSE_IDX = {
    "LEFT_WRIST": 15, "RIGHT_WRIST": 16,
    "LEFT_PINKY": 17, "RIGHT_PINKY": 18,
    "LEFT_INDEX": 19, "RIGHT_INDEX": 20,
    # （如果想更稳一点，LEFT_THUMB=21, RIGHT_THUMB=22 也可参考，但这里不使用拇指）
}

def median_point(points):
    if not points:
        return None
    xs = sorted(p[0] for p in points)
    ys = sorted(p[1] for p in points)
    m = len(points)//2
    return (xs[m], ys[m])

class Swipe(BaseAction):
    # 统一的“动作类名”（内部用），但对外的 label 动态按方向返回
    label = "swipe"

    def __init__(self, win_ms=500, cooldown_ms=1000, dir_ratio=1.8,
                 v_min=2.8, d_min_scale=0.08, vis_min=0.5, max_hist_ms=1500,
                 reset_suppress_ms=400, reset_d_max=0.5):
        self.win_ms = win_ms
        self.cooldown_ms = cooldown_ms
        self.dir_ratio = dir_ratio
        self.v_min = v_min
        self.d_min_scale = d_min_scale
        self.vis_min = vis_min
        self.max_hist_ms = max_hist_ms
        self.reset_suppress_ms = reset_suppress_ms
        self.reset_d_max = reset_d_max

    def _get_state_buf(self, get_state, person_id, hand_idx):
        # 先拿到该人的总状态容器（是一个持久 dict）
        person_state = get_state(person_id)

        # 在这个 dict 内，为当前手的滑动历史建立一个子键
        # 用共享前缀，避免不同方向维护多份历史
        sub_key = f"swipe_shared:h{hand_idx}"

        st = person_state.get(sub_key)
        if st is None:
            st = {"hist": deque(), "last_fire_ms": -10**9, "last_update_ms": -10**9}
            person_state[sub_key] = st

        return st
    
    def _update_history(self, st, t_ms, center, palm_w):
        if t_ms == st.get("last_update_ms"):
            return
        hist = st["hist"]
        hist.append((t_ms, center[0], center[1], palm_w))
        st["last_update_ms"] = t_ms

        t_min = t_ms - self.max_hist_ms
        while hist and hist[0][0] < t_min:
            hist.popleft()

    def _window_points(self, hist, t_ms, win_ms):
        # 要求至少 3 个样本
        if not hist or len(hist) < 3:
            return None, None

        t0 = t_ms - win_ms
        # 使用 50/50 切分，加小重叠
        split = t0 + 0.5 * win_ms
        eps = max(5, int(win_ms * 0.05))  # 至少 5ms 的重叠

        head = [(x, y) for (t, x, y, w) in hist if t0 <= t <= split + eps]
        tail = [(x, y) for (t, x, y, w) in hist if split - eps <= t <= t_ms]

        # 如果 head 或 tail 为空，退化到“最近可用点”的中位
        if not head:
            head = [(x, y) for (t, x, y, w) in hist if t0 <= t <= t0 + 0.7 * win_ms]
        if not tail:
            tail = [(x, y) for (t, x, y, w) in hist if t0 + 0.3 * win_ms <= t <= t_ms]

        if not head or not tail:
            return None, None

        return median_point(head), median_point(tail)
    
    def _get_hand_landmarks(self, lms):
        """
        lms: list[NormalizedLandmark] 长度 33 (MediaPipe Pose)
        返回 hands_out: 每只手 1 个“手”的结构：
        { "visible": True, "tips": [(x,y), ...], "palm_w": float }
        在只有 Pose 时，我们只能用“食指尖”作为 tips 列表，其它三指不可得。
        """
        hands_out = []
        if not isinstance(lms, (list, tuple)) or len(lms) < 21:
            return hands_out

        def get_xyv(i):
            p = lms[i]
            # 适配对象属性（mediapipe 的 NormalizedLandmark）
            x = getattr(p, "x", None)
            y = getattr(p, "y", None)
            vis = getattr(p, "visibility", 1.0)
            return (x, y, vis)

        # 遍历左右手
        for side in ("RIGHT", "LEFT"):
            wrist_i = POSE_IDX[f"{side}_WRIST"]
            index_i = POSE_IDX[f"{side}_INDEX"]
            pinky_i = POSE_IDX[f"{side}_PINKY"]

            wx, wy, wv = get_xyv(wrist_i)
            ix, iy, iv = get_xyv(index_i)
            px, py, pv = get_xyv(pinky_i)

            # 可见性检查（阈值可调）
            if ix is None or iy is None or iv < self.vis_min:
                continue
            if wx is None or wy is None or wv < 0.2:
                # wrist 不可见时，仍可以尝试使用一个固定掌宽兜底
                wrist_ok = False
            else:
                wrist_ok = True

            # tips：仅用食指尖（Pose 无法得到中指/无名指尖）
            tips = [(ix, iy)]

            # palm_w 估算：优先 wrist-pinky 或 wrist-index 的距离
            if wrist_ok and px is not None and py is not None and pv > 0.2:
                palm_w = math.hypot(wx - px, wy - py)
            elif wrist_ok:
                # 回退：wrist-index 的距离乘以一个经验系数（让尺度更接近掌宽）
                palm_w = 1.6 * math.hypot(wx - ix, wy - iy)
            else:
                # 最后回退：使用常数（归一化坐标下）
                palm_w = 0.06

            # 防止异常小值
            if not palm_w or palm_w < 1e-4:
                palm_w = 0.06

            hands_out.append({
                "visible": True,
                "tips": tips,
                "palm_w": palm_w
            })

        return hands_out

    def _dir_from_displacement(self, ndx, ndy, dir_ratio):
        ax, ay = abs(ndx), abs(ndy)
        if ax >= ay * dir_ratio:
            return ("swipe_right" if ndx > 0 else "swipe_left"), ax
        if ay >= ax * dir_ratio:
            return ("swipe_down" if ndy > 0 else "swipe_up"), ay
        return (None, 0.0)

    def _should_fire(self, st, t_ms):
        return (t_ms - st["last_fire_ms"]) >= self.cooldown_ms

    def _is_reset_motion(self, st, cand_label, dist_norm, t_ms):
        # 与上次相反方向且为复位的小幅运动则抑制
        opp = {"swipe_left":"swipe_right","swipe_right":"swipe_left",
               "swipe_up":"swipe_down","swipe_down":"swipe_up"}
        last = st.get("last_label")
        if last and opp.get(last) == cand_label:
            if (t_ms - st["last_fire_ms"]) <= self.reset_suppress_ms and dist_norm <= self.reset_d_max:
                return True
        return False

    def score(self, lms, person_id, get_state):
        t_ms = int(time.time() * 1000)
        hands = self._get_hand_landmarks(lms)
        best_score = 0.0
        best_label = None

        for hi, hand in enumerate(hands):
            st = self._get_state_buf(get_state, person_id, hi)
            c = median_point(hand["tips"])
            if c is None:
                continue

            self._update_history(st, t_ms, c, hand["palm_w"])
            hist = st["hist"]

            p0, p1 = self._window_points(hist, t_ms, self.win_ms)
            if p0 is None or p1 is None:
                continue

            dx = p1[0] - p0[0]
            dy = p1[1] - p0[1]

            # 归一化
            palms = [w for (_,_,_,w) in hist]
            palm_w = sorted(palms)[len(palms)//2] if palms else hand["palm_w"]
            if palm_w < 1e-4: palm_w = 0.05
            ndx, ndy = dx / palm_w, dy / palm_w

            dt = max(self.win_ms, 1) / 1000.0
            dist = math.hypot(ndx, ndy)
            speed = dist / dt

            cand_label, main_axis = self._dir_from_displacement(ndx, ndy, self.dir_ratio)
            if not cand_label:
                continue

            if speed >= self.v_min and dist >= self.d_min_scale:
                if not self._should_fire(st, t_ms):
                    continue
                if self._is_reset_motion(st, cand_label, dist, t_ms):
                    continue

                # 触发一次，记录并计算分数
                st["last_fire_ms"] = t_ms
                st["last_label"] = cand_label
                s = min(1.0, 0.5 + 0.5 * min(1.0, speed/self.v_min))
                if s > best_score:
                    best_score = s
                    best_label = cand_label

        if best_score > 0 and best_label:
            return best_score, {"label": best_label}

        return 0.0, {}